package com.ww.springboot.es.app.search.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ww.springboot.es.app.search.service.IEsSearchToolSvc;
import com.ww.springboot.es.dto.EsQueryDTO;
import com.ww.springboot.es.dto.ForecastRecord;
import com.ww.springboot.es.dto.ForecastRecordDTO;
import com.ww.springboot.es.en.ForecastBizTypeEnum;
import com.ww.springboot.es.resp.ForecastRecordRepository;
import com.ww.springboot.es.utils.ElasticsearchJsonUtil;
import com.ww.springboot.es.utils.ElasticsearchTemplateUtil;
import com.ww.springboot.es.utils.JsonPointUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.Charsets;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.client.RequestOptions;

import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static java.util.stream.Collectors.joining;

/**
 * 描述：
 *
 * @author 🧑 ‍wanwei
 * @since 2025-07-30 15:53
 */
@Slf4j
@Service
@AllArgsConstructor
public class EsSearchToolSvcImpl implements IEsSearchToolSvc {

    private final ForecastRecordRepository repository;

    private static final DeprecationHandler DEPRECATION_HANDLER = new DeprecationHandler() {
        @Override
        public void usedDeprecatedName(String usedName, String modernName) {
        }

        @Override
        public void usedDeprecatedField(String usedName, String replacedWith) {
        }
    };

    @Override
    public void exportWithApi(HttpServletResponse response, EsQueryDTO esQueryDTO) throws IOException {
        String jsonStr = JSONUtil.toJsonStr(esQueryDTO.getQueryJson());
        NativeSearchQueryBuilder queryBuilder = ElasticsearchJsonUtil.fromJson(jsonStr);
        int page = 0;
        int size = 20;
        List<List<String>> list = new ArrayList<>();
        list.add(this.getHeadList(jsonStr));
        ElasticsearchRestTemplate elasticsearchRestTemplate = ElasticsearchTemplateUtil.build(esQueryDTO.getProperties());
        while (true) {
            queryBuilder.withPageable(PageRequest.of(page, size));
            SearchRequest searchRequest = ElasticsearchJsonUtil.getSearchRequest(queryBuilder.build(), esQueryDTO.getIndexName());
            SearchResponse searchResponse = elasticsearchRestTemplate.execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
            SearchDocumentResponse searchDocumentResponse = SearchDocumentResponse.from(searchResponse);
            if (ObjectUtil.isEmpty(searchDocumentResponse)) {
                break;
            }
            if (ObjectUtil.isEmpty(searchDocumentResponse.getSearchDocuments())) {
                break;
            }
            this.addData(page, searchDocumentResponse, list);
            log.info("exportWithApi total: {}, page: {}", searchDocumentResponse.getTotalHits(), page);
            page++;
        }
        this.exportData(response, list);
    }

    @Override
    public void exportWithApiAndScroll(HttpServletResponse response, EsQueryDTO esQueryDTO) throws IOException {
        String jsonStr = JSONUtil.toJsonStr(esQueryDTO.getQueryJson());
        NativeSearchQueryBuilder queryBuilder = ElasticsearchJsonUtil.fromJson(jsonStr);
        int page = 0;
        int size = 20;
        List<List<String>> list = new ArrayList<>();
        list.add(this.getHeadList(jsonStr));
        ElasticsearchRestTemplate elasticsearchRestTemplate = ElasticsearchTemplateUtil.build(esQueryDTO.getProperties());
        String scrollId = "";
        while (true) {
            if(ObjectUtil.isEmpty(scrollId)){
                queryBuilder.withPageable(PageRequest.of(page, size));
                SearchRequest searchRequest = ElasticsearchJsonUtil.getSearchRequest(queryBuilder.build(), esQueryDTO.getIndexName());
                searchRequest.scroll(TimeValue.timeValueMinutes(15L));
                SearchResponse searchResponse = elasticsearchRestTemplate.execute(client -> client.search(searchRequest, RequestOptions.DEFAULT));
                SearchDocumentResponse searchDocumentResponse = SearchDocumentResponse.from(searchResponse);
                if (ObjectUtil.isEmpty(searchDocumentResponse)) {
                    break;
                }
                if (ObjectUtil.isEmpty(searchDocumentResponse.getSearchDocuments())) {
                    break;
                }
                scrollId = searchResponse.getScrollId();
                this.addData(page, searchDocumentResponse, list);
                log.info("exportWithApi total: {}, page: {},scrollId: {}", searchDocumentResponse.getTotalHits(), page, scrollId);
            }else {
                SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
                searchScrollRequest.scroll(TimeValue.timeValueMinutes(15L));
                SearchResponse searchResponse = elasticsearchRestTemplate.execute(client -> client.scroll(searchScrollRequest, RequestOptions.DEFAULT));
                SearchDocumentResponse searchDocumentResponse = SearchDocumentResponse.from(searchResponse);
                if (ObjectUtil.isEmpty(searchDocumentResponse)) {
                    break;
                }
                if (ObjectUtil.isEmpty(searchDocumentResponse.getSearchDocuments())) {
                    break;
                }
                this.addData(page, searchDocumentResponse, list);
                log.info("exportWithApi total: {}, page: {},scrollId: {}", searchDocumentResponse.getTotalHits(), page, scrollId);
            }
            page++;
        }
        //清除快照
        if(ObjectUtil.isNotEmpty(scrollId)){
            ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
            clearScrollRequest.addScrollId(scrollId);
            elasticsearchRestTemplate.execute(client -> client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT));
        }
        this.exportData(response, list);
    }

    @Override
    public void exportWithHttp(HttpServletResponse response, EsQueryDTO esQueryDTO) throws IOException {
        String jsonStr = JSONUtil.toJsonStr(esQueryDTO.getQueryJson());
        ObjectNode objectNode = JsonPointUtil.getObjectNode(jsonStr);
        int page = 0;
        int size = 20;
        objectNode.put("size", size);
        List<List<String>> list = new ArrayList<>();
        list.add(this.getHeadList(jsonStr));
        String urlFormat = "http://%s:5601/api/console/proxy?path=%s/_search&method=GET";
        String url = String.format(urlFormat, esQueryDTO.getHost(), esQueryDTO.getIndexName());
        while (true) {
            objectNode.put("from", page * size);
            InputStream inputStream = HttpRequest.post(url)
                    .header("kbn-version", "7.6.2")
                    .body(objectNode.toString())
                    .execute()
                    .bodyStream();
            XContentType xContentType = XContentType.fromMediaTypeOrFormat("application/json; charset=UTF-8");

            SearchDocumentResponse searchDocumentResponse = null;
            try (XContentParser parser = xContentType.xContent().createParser(new NamedXContentRegistry(new ArrayList<>()),
                    DEPRECATION_HANDLER, inputStream)) {
                SearchResponse searchResponse = SearchResponse.fromXContent(parser);
                if(searchResponse == null || searchResponse.getHits() == null){
                    break;
                }
                searchDocumentResponse = SearchDocumentResponse.from(searchResponse);
            }

            if (ObjectUtil.isEmpty(searchDocumentResponse)) {
                break;
            }
            if (ObjectUtil.isEmpty(searchDocumentResponse.getSearchDocuments())) {
                break;
            }

            this.addData(page, searchDocumentResponse, list);
            log.info("exportWithHttp total: {}, page: {}", searchDocumentResponse.getTotalHits(), page);
            page++;
        }

        if(ObjectUtil.isNotEmpty(esQueryDTO.getDealType())){
            switch (esQueryDTO.getDealType()){
                case "1":
                    this.syncOmPackageToEs(list);
                    break;
            }
        }else {
            this.exportData(response, list);
        }
    }

    @Override
    public void exportWithHttpAggs(HttpServletResponse response, EsQueryDTO esQueryDTO) throws IOException {
        String jsonStr = JSONUtil.toJsonStr(esQueryDTO.getQueryJson());
        ObjectNode objectNode = JsonPointUtil.getObjectNode(jsonStr);
        int page = 0;
        int size = 20;
        objectNode.put("size", size);
        List<List<String>> list = new ArrayList<>();
        list.add(this.getHeadList(jsonStr));
        String urlFormat = "http://%s:5601/api/console/proxy?path=%s/_search&method=GET";
        String url = String.format(urlFormat, esQueryDTO.getHost(), esQueryDTO.getIndexName());
        String resp = HttpRequest.post(url)
                .header("kbn-version", "7.6.2")
                .body(objectNode.toString())
                .execute()
                .body();

        System.out.println(resp);
        JSONObject jsonObject = JSON.parseObject(resp);
        //this.exportData(response, list);
    }

    private void syncOmPackageToEs(List<List<String>> list) {
        if(list.size() == 1){
            return;
        }
        for (List<String> data : list) {
            String bodyFormat = "{\n" +
                    "    \"packageCode\": \"%s\",\n" +
                    "    \"tenantId\": \"%s\",\n" +
                    "    \"omOrderId\": \"\"\n" +
                    "}";
            String body = String.format(bodyFormat, data.get(0), data.get(1));
            System.out.println(HttpRequest.post("http://oms-service.box-tools.com/test/push2om")
                    .body(body)
                    .execute()
                    .body());
        }
    }


    @Override
    public Page<ForecastRecord> demo(PageRequest pageRequest, ForecastRecordDTO forecastRecord) {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(getQueryBuilder(forecastRecord))
                .withSort(getSortBuilder())
                .withPageable(pageRequest)
                .build();

        // 分页参数
        Page<ForecastRecord> searchPageResults = repository.search(query);
        searchPageResults.getContent().stream().forEach(record -> {
            record.setBizTypeName(ForecastBizTypeEnum.getTextByCode(record.getBizType()));
        });
        return searchPageResults;
    }

    /**
     * 字段排序
     *
     * @return
     */
    private SortBuilder getSortBuilder() {
        return SortBuilders.fieldSort("createTime").order(SortOrder.DESC);
    }

    /**
     * //组合查询
     *
     * @param forecastRecord
     * @return
     */
    private BoolQueryBuilder getQueryBuilder(ForecastRecordDTO forecastRecord) {
        //组合查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        if (ObjectUtil.isNotEmpty(forecastRecord.getBizNumber())) {
            queryBuilder.must(QueryBuilders.matchQuery("bizNumber", forecastRecord.getBizNumber()));
        }
        if (ObjectUtil.isNotEmpty(forecastRecord.getStatus())) {
            queryBuilder.must(QueryBuilders.matchQuery("status", forecastRecord.getStatus()));
        }
        if (ObjectUtil.isNotEmpty(forecastRecord.getRequestSysName())) {
            queryBuilder.must(QueryBuilders.matchQuery("requestSysName", forecastRecord.getRequestSysName()));
        }
        if (ObjectUtil.isNotEmpty(forecastRecord.getResponseSysName())) {
            queryBuilder.must(QueryBuilders.matchQuery("responseSysName", forecastRecord.getResponseSysName()));
        }
        if (ObjectUtil.isNotEmpty(forecastRecord.getBizType())) {
            queryBuilder.must(QueryBuilders.matchQuery("bizType", forecastRecord.getBizType()));
        }
        if (ObjectUtil.isNotEmpty(forecastRecord.getCreateTimeBegin()) || ObjectUtil.isNotEmpty(forecastRecord.getCreateTimeEnd())) {
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("createTime");
            if (ObjectUtil.isNotEmpty(forecastRecord.getCreateTimeBegin())) {
                rangeQueryBuilder.gte(changeToLocalDateTime(forecastRecord.getCreateTimeBegin()));
            }
            if (ObjectUtil.isNotEmpty(forecastRecord.getCreateTimeEnd())) {
                rangeQueryBuilder.lte(changeToLocalDateTime(forecastRecord.getCreateTimeEnd()));
            }
            queryBuilder.must(rangeQueryBuilder);
        }

        if (ObjectUtil.isNotEmpty(forecastRecord.getUpdateTimeBegin()) || ObjectUtil.isNotEmpty(forecastRecord.getUpdateTimeEnd())) {
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("updateTime");
            if (ObjectUtil.isNotEmpty(forecastRecord.getUpdateTimeBegin())) {
                rangeQueryBuilder.gte(changeToLocalDateTime(forecastRecord.getUpdateTimeBegin()));
            }
            if (ObjectUtil.isNotEmpty(forecastRecord.getUpdateTimeEnd())) {
                rangeQueryBuilder.lte(changeToLocalDateTime(forecastRecord.getUpdateTimeEnd()));
            }
            queryBuilder.must(rangeQueryBuilder);
        }
        return queryBuilder;
    }


    private String changeToLocalDateTime(String timeStr) {
        return LocalDateTime.parse(timeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).withNano(0).toString();
    }

    private static void addData(int page, SearchDocumentResponse searchDocumentResponse, List<List<String>> list) {
        if (page == 0 && list.size() == 0) {
            list.add(new ArrayList<>(searchDocumentResponse.getSearchDocuments().get(0).keySet()));
        }
        List<String> headList = list.get(0);
        for (SearchDocument searchDocument : searchDocumentResponse.getSearchDocuments()) {
            List<String> line = new ArrayList<>();
            for (String column : headList) {
                Object o = searchDocument.get(column);
                if (o instanceof List) {
                    line.add(((ArrayList<?>) o).stream().map(i->String.valueOf(i)).collect(joining(",")));
                } else {
                    line.add(Optional.ofNullable(o).map(Object::toString).orElse(""));
                }
            }
            list.add(line);
        }
    }

    private static void exportData(HttpServletResponse response, List<List<String>> list) throws IOException {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding(Charsets.UTF_8.name());
        String fileName = "ES-EXPORT_" + System.currentTimeMillis();
        String sheetName = "列表";
        fileName = URLEncoder.encode(fileName, Charsets.UTF_8.name());
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream()).sheet(sheetName).doWrite(list);
    }

    private static List<String> getHeadList(String esQueryDTO) {
        Object o = JsonPointUtil.jsonPoint(esQueryDTO, "/_source");
        if (o == null) {
            return new ArrayList<>();
        }
        if (o instanceof List) {
            return (List<String>) o;
        }
        return new ArrayList<>();
    }
}
